//Date: 2023-11-26 00:21:46
//LastEditors: jie zhengjie_liang@qq.com
//LastEditTime: 2023-11-26 00:22:24
//FilePath: /photowall/pkg/main/options/staticfile_db_opt.go

package options

import (
	"fmt"
	"photowall/pkg/config"
	"photowall/pkg/log"
	"photowall/pkg/objdef"
	"strconv"
	"sync"
	"time"

	"github.com/jmoiron/sqlx"
)

var (
	queryStaticFilePreparex         *sqlx.Stmt
	queryStaticFilePreparexSyncOnce sync.Once
)

//description: 通过md5 从数据库查询是否有文件，是则返回文件的指针，否则返回空指针
//param {string} md5
//return {*}

func getFileByMD5FromDatabase(md5 string) *objdef.FileMetadata {
	queryStaticFilePreparexSyncOnce.Do(func() {
		stmt, err := GetMainSqlConn().Preparex("select * from static_file where file_md5 = ?;")
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error())
			panic(err)
		}

		queryStaticFilePreparex = stmt
	})

	var fileMetadata objdef.FileMetadata
	err := queryStaticFilePreparex.Get(&fileMetadata, md5)
	if err != nil {
		log.GetDatabaseLogger().Error(err.Error() + " :file get failed " + md5)
		return nil
	}

	log.GetDatabaseLogger().Info(fmt.Sprintf("search file in db: %v", md5))
	return &fileMetadata
}

var (
	countStaticFilePreparex         *sqlx.Stmt
	countStaticFilePreparexSyncOnce sync.Once
)

//description: 查看该md5文件的计数
//param {string} md5
//return {*}

func getFileCountByMD5FromDatbase(md5 string) int {
	countStaticFilePreparexSyncOnce.Do(func() {
		stmt, err := GetMainSqlConn().Preparex("select count(*) from static_file where file_md5 = ?;")
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error())
			panic(err)
		}

		countStaticFilePreparex = stmt
	})

	var count int
	err := countStaticFilePreparex.Get(&count, md5)
	if err != nil {
		log.GetDatabaseLogger().Error(err.Error() + "file get failed " + md5)
		return -1
	}

	return count
}

var (
	isStaticFileExistPreparex         *sqlx.Stmt
	isStaticFileExistPreparexSyncOnce sync.Once
)

//description: 通过md5查询是否存在指定的文件
//param {string} md5
//return {*}

func isFileExistInDB(md5 string) bool {
	isStaticFileExistPreparexSyncOnce.Do(func() {
		stmt, err := GetMainSqlConn().Preparex("SELECT EXISTS(SELECT 1 FROM static_file WHERE file_md5 = ?);")
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error())
			panic(err)
		}

		isStaticFileExistPreparex = stmt
	})

	var exist bool
	err := isStaticFileExistPreparex.Get(&exist, md5)
	if err != nil {
		log.GetDatabaseLogger().Error("Failed to check if file with MD5" + md5 + "exists in DB:" + err.Error())
		return false
	}

	return exist
}

var (
	insertStaticFilestmt         *sqlx.Stmt
	insertStaticFilestmtSyncOnce sync.Once

	updateStaticFileCitationCountStmt *sqlx.Stmt
	// updateStaticFileCitationCountStmtSyncOnce sync.Once
)

//description: 传入一个FileMetadata指针，将该指针对应的文件插入到sql中，如果已经存在该md5，则增加其引用次数
//param {*objdef.FileMetadata} fileMetadata
//return {*}

func insertStaticFileToDB(fileMetadata *objdef.FileMetadata) bool {
	insertStaticFilestmtSyncOnce.Do(func() {
		stmt, err := GetMainSqlConn().Preparex("insert into static_file values(?, ?, ?, ?);")
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error() + "insert preparex failed")
			panic(err)
		}

		insertStaticFilestmt = stmt

		updateCitaionCountstmt, err := GetMainSqlConn().Preparex("update static_file set citation_count=citation_count + 1 where file_md5=?")
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error() + "update preparex failed")
			panic(err)
		}

		updateStaticFileCitationCountStmt = updateCitaionCountstmt
	})

	// 如果该md5的文件已经存在，则增加其引用次数
	if isFileExistInDB(fileMetadata.FileMD5) {
		res, err := updateStaticFileCitationCountStmt.Exec(fileMetadata.FileMD5)
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error() + " update Exec errror")
			return false
		}

		affected_row, _ := res.RowsAffected()
		if affected_row == 1 {
			return true
		} else {
			log.GetDatabaseLogger().Error("update staitc file citation count panic, RowsAffected is not equal to 1. is: " + strconv.FormatInt(affected_row, 10))
			return false
		}

	}

	// 否则直接插入新记录
	res, err := insertStaticFilestmt.Exec(fileMetadata.FileMD5, fileMetadata.FileType,
		fileMetadata.Optimized, fileMetadata.CitationCount)
	if err != nil {
		log.GetDatabaseLogger().Error(err.Error() + "insert file failed, the md5 is:" + fileMetadata.FileMD5)
		return false
	}

	affectedRow, _ := res.RowsAffected()
	if affectedRow == 1 {
		return true
	} else {
		log.GetDatabaseLogger().Error(err.Error() + "insert file failed, the md5 is:" + fileMetadata.FileMD5)
		return false
	}

}

var (
	deleteStaticFileStmt         *sqlx.Stmt
	deleteStaticFileStmtSyncOnce sync.Once

	reduceCitationCountStmt *sqlx.Stmt
)

//description: 通过md5删除一个文件，如果文件引用次数高于1，则减少其引用次数，如果等于1，则删除该文件记录
//param {string} md5
//return {*}

func deleteStaticFileInDB(md5 string) bool {
	deleteStaticFileStmtSyncOnce.Do(func() {
		stmt, err := GetMainSqlConn().Preparex("delete from static_file where file_md5 = ?;")
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error())
			panic(err)
		}

		deleteStaticFileStmt = stmt

		stmt1, err := GetMainSqlConn().Preparex("update static_file set citation_count=citation_count-1 where file_md5 = ? and citation_count > 1;")
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error())
			panic(err)
		}

		reduceCitationCountStmt = stmt1
	})

	// 获取该md5的文件的引用次数
	citationCount := getStaticFileCitationCount(md5)
	if citationCount == -1 {
		// 文件不存在，直接返回成功
		return true

	} else if citationCount == 1 {
		// 引用次数为1的时候，直接删除该记录
		res, err := deleteStaticFileStmt.Exec(md5)
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error())
			return false
		}

		affectedRow, _ := res.RowsAffected()
		if affectedRow == 1 {
			return true
		} else if affectedRow > 1 {
			log.GetDatabaseLogger().Error("delete file by md5 more than one record affected: " + strconv.Itoa(int(affectedRow)))
			return true
		} else {
			log.GetDatabaseLogger().Error(err.Error() + " delete file by md5, but no record affercted")
			return true
		}

	} else if citationCount > 1 {
		// 引用次数大于1，减少其引用次数
		res, err := reduceCitationCountStmt.Exec(md5)
		if err != nil {
			log.GetDatabaseLogger().Error(err.Error())
			return false
		}

		affectedRow, _ := res.RowsAffected()
		if affectedRow == 1 {
			return true
		} else {
			log.GetDatabaseLogger().Error("try to reduce the citation count by md5, but affctedRow is not 1")
			return true
		}

	} else {
		// 其他情况
		log.GetDatabaseLogger().Error("try to deleteStaticFile but citation_count is < 1")
		return true
	}
}

var (
	getStaticFileCitationCountStmt         *sqlx.Stmt
	getStaticFileCitationCountStmtSyncOnce sync.Once
)

//description: 通过md5 来获取一个文件的引用次数
//param {string} md5
//return {*}

func getStaticFileCitationCount(md5 string) int {
	getStaticFileCitationCountStmtSyncOnce.Do(func() {
		stmt, err := GetMainSqlConn().Preparex("select citation_count from static_file where file_md5=?;")
		if err != nil {
			log.GetDatabaseLogger().Error("Failed to prepare SQL statement:" + err.Error())
			panic(err)
		}

		getStaticFileCitationCountStmt = stmt
	})

	var res int
	err := getStaticFileCitationCountStmt.Get(&res, md5)
	if err != nil {
		log.GetDatabaseLogger().Error("Failed to get citation count for file with MD5" + md5 + ":" + err.Error())
		return -1
	}

	return res
}

//description: 将文件记录直接插入到redis中,如果没有被锁定的话
//param {*objdef.FileMetadata} fileMetadata
//return {*}

func putFileMetadataIntoRedis(fileMetadata *objdef.FileMetadata) bool {
	if fileMetadata == nil {
		return false
	}

	res, _ := GetRedisClient().HGet("staitcfile:"+fileMetadata.FileMD5, "locked").Result()
	locked := res == "1"

	if locked {
		return true
	}

	fields := map[string]interface{}{
		"locked": false,
	}
	err := GetRedisClient().HMSet("staitcfile:"+fileMetadata.FileMD5, fields).Err()
	if err != nil {
		log.GetRedisLogger().Error(fmt.Sprintf("can not HMSet %v", fields))
		return false
	}

	expiration := time.Duration(config.GetConfig().FileSaveConfig.ImageConfig.RedisExpireTime) * time.Second
	if err := GetRedisClient().Expire("staitcfile:"+fileMetadata.FileMD5, expiration).Err(); err != nil {
		log.GetRedisLogger().Error("Failed to set expiration time: %v", err)
		return true
	}

	return true
}
